/*
 * Copyright (c) 2014 pkzd/1.0, 颜文泽(robin)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1 Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     2 Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include	<map.h>
#include	<page.h>
#include	<proc.h>

uint page_list_lock;
struct page_list *pfreelist;
struct page_list *pusedlist;

void
page_list_init(void)
{
	page_list_lock = 0;
	pfreelist = pusedlist = NULL;
}

void
page_list_rm(struct page_list *p)
{
	if(p)
		mfree(kernmap, 1, p->p_head);
}

int
page_list_add(void)
{
	struct page_list *p, *p0, *p1;

	if(!(p = malloc(kernmap, 1)))
		return -1;
	p->p_cnt = PAGE_LIST_NUM;
	for(p0 = p + 1, p1 = p0 + PAGE_LIST_NUM; p0 < p1; p0++){
		p0->p_head = p;
		p0->p_cnt = p0->p_addr = 0;
		p0->p_next = pfreelist;
		pfreelist = p0;
	}
	return 0;
}

struct page_list *
page_list_alloc(uint addr)
{
	struct page_list *p, *rp;

	if(!addr)
		return NULL;
	while(page_list_lock)
		sleep(&page_list_lock, PBUF);
	page_list_lock = 1;
	rp = NULL;
	for(p = pusedlist; p; p = p->p_next){
		if(p->p_cnt && p->p_addr == addr){
			p->p_cnt++;
			goto out;
		}
		if(!rp && p->p_addr == 0)
			rp = p;
	}
	if(p = rp){
		p->p_cnt = 1;
		p->p_addr = addr;
		goto out;
	}
loop:
	if(p = pfreelist){
		p->p_cnt = 1;
		p->p_addr = addr;
		p->p_head->p_cnt--;
		pfreelist = p->p_next;
		p->p_next = pusedlist;
		pusedlist = p;
		goto out;
	}
	if(!page_list_add())
		goto loop;
	return NULL;
out:
	page_list_lock = 0;
	wakeup(&page_list_lock);
	return p;
}

struct page_list *
page_list_get(uint addr)
{
	struct page_list *p;

	if(!addr)
		return NULL;
	for(p = pusedlist; p; p = p->p_next)
		if(p->p_cnt && p->p_addr == addr)
			return p;
	return NULL;
}

struct page_list *
page_list_free(uint addr)
{
	struct page_list *p, *rp;

	if(!addr)
		return NULL;
	while(page_list_lock)
		sleep(&page_list_lock, PBUF);
	page_list_lock = 1;
	rp = NULL;
	for(p = pusedlist; p; p = p->p_next){
		if(p->p_cnt && p->p_addr == addr){
			if(p->p_cnt == 1)
				p->p_addr = 0;	
			p->p_cnt--;
			rp = p;
			break;
		}
	}
	while(pusedlist){
		p = pusedlist;
		if(p->p_addr == 0){
			pusedlist = p->p_next;
			p->p_next = pfreelist;
			pfreelist = p;
			if(++p->p_head->p_cnt == PAGE_LIST_NUM)
				page_list_rm(p);
		}else
			break;	
	}
	page_list_lock = 0;
	wakeup(&page_list_lock);
	return rp;
}
